/* strutil.c
 * String utility routines
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include <glib.h>
#include "strutil.h"

#include "ws_codepoints.h"
#include "wmem_strbuf.h"
#include "unicode-utils.h"
#include "utf8_entities.h"

#include "proto.h"

/*
 * If a user _does_ pass in a too-small buffer, this is probably
 * going to be too long to fit.  However, even a partial string
 * starting with "[Buf" should provide enough of a clue to be
 * useful.
 */
#define _return_if_nospace(str_len, buf, buf_len) \
    do { \
        if ((str_len) > (buf_len)) { \
            (void)g_strlcpy(buf, "[Buffer too small]", buf_len); \
            return; \
        } \
    } while (0)

static const char fast_strings[][4] = {
    "0", "1", "2", "3", "4", "5", "6", "7",
    "8", "9", "10", "11", "12", "13", "14", "15",
    "16", "17", "18", "19", "20", "21", "22", "23",
    "24", "25", "26", "27", "28", "29", "30", "31",
    "32", "33", "34", "35", "36", "37", "38", "39",
    "40", "41", "42", "43", "44", "45", "46", "47",
    "48", "49", "50", "51", "52", "53", "54", "55",
    "56", "57", "58", "59", "60", "61", "62", "63",
    "64", "65", "66", "67", "68", "69", "70", "71",
    "72", "73", "74", "75", "76", "77", "78", "79",
    "80", "81", "82", "83", "84", "85", "86", "87",
    "88", "89", "90", "91", "92", "93", "94", "95",
    "96", "97", "98", "99", "100", "101", "102", "103",
    "104", "105", "106", "107", "108", "109", "110", "111",
    "112", "113", "114", "115", "116", "117", "118", "119",
    "120", "121", "122", "123", "124", "125", "126", "127",
    "128", "129", "130", "131", "132", "133", "134", "135",
    "136", "137", "138", "139", "140", "141", "142", "143",
    "144", "145", "146", "147", "148", "149", "150", "151",
    "152", "153", "154", "155", "156", "157", "158", "159",
    "160", "161", "162", "163", "164", "165", "166", "167",
    "168", "169", "170", "171", "172", "173", "174", "175",
    "176", "177", "178", "179", "180", "181", "182", "183",
    "184", "185", "186", "187", "188", "189", "190", "191",
    "192", "193", "194", "195", "196", "197", "198", "199",
    "200", "201", "202", "203", "204", "205", "206", "207",
    "208", "209", "210", "211", "212", "213", "214", "215",
    "216", "217", "218", "219", "220", "221", "222", "223",
    "224", "225", "226", "227", "228", "229", "230", "231",
    "232", "233", "234", "235", "236", "237", "238", "239",
    "240", "241", "242", "243", "244", "245", "246", "247",
    "248", "249", "250", "251", "252", "253", "254", "255"
};

static bool is_byte_sep(uint8_t c)
{
    return (c == '-' || c == ':' || c == '.');
}

/* Turn a string of hex digits with optional separators (defined by
 * is_byte_sep() into a byte array.
 *
 * XXX - This function is perhaps too generous in what it accepts.
 * It allows the separator to change from one character to another,
 * or to and from no separator if force_separators is false.
 */
bool hex_str_to_bytes(const char *hex_str, GByteArray *bytes, bool force_separators)
{
    uint8_t       val;
    const char     *p, *q, *r, *s, *punct;
    char        four_digits_first_half[3];
    char        four_digits_second_half[3];
    char        two_digits[3];
    char        one_digit[2];

    if (! hex_str || ! bytes) {
        return false;
    }
    g_byte_array_set_size(bytes, 0);
    p = hex_str;
    while (*p) {
        q = p+1;
        r = p+2;
        s = p+3;

        if (*q && *r
                && g_ascii_isxdigit(*p) && g_ascii_isxdigit(*q) &&
                g_ascii_isxdigit(*r)) {

            /*
             * Three hex bytes in a row, followed by a non hex byte
             * (possibly the end of the string). We don't accept an
             * odd number of hex digits except for single digits
             * by themselves or after a separator.
             */
            if (!g_ascii_isxdigit(*s)) {
                return false;
            }
            four_digits_first_half[0] = *p;
            four_digits_first_half[1] = *q;
            four_digits_first_half[2] = '\0';
            four_digits_second_half[0] = *r;
            four_digits_second_half[1] = *s;
            four_digits_second_half[2] = '\0';

            /*
             * Four or more hex digits in a row.
             */
            val = (uint8_t) strtoul(four_digits_first_half, NULL, 16);
            g_byte_array_append(bytes, &val, 1);
            val = (uint8_t) strtoul(four_digits_second_half, NULL, 16);
            g_byte_array_append(bytes, &val, 1);

            punct = s + 1;
            if (*punct) {
                /*
                 * Make sure the character after
                 * the fourth hex digit is a byte
                 * separator, i.e. that we don't have
                 * more than four hex digits, or a
                 * bogus character.
                 */
                if (is_byte_sep(*punct)) {
                    p = punct + 1;
                    continue;
                }
                else if (force_separators) {
                    return false;
                }
            }
            p = punct;
            continue;
        }
        else if (*q && g_ascii_isxdigit(*p) && g_ascii_isxdigit(*q)) {
            two_digits[0] = *p;
            two_digits[1] = *q;
            two_digits[2] = '\0';

            /*
             * Two hex digits in a row.
             */
            val = (uint8_t) strtoul(two_digits, NULL, 16);
            g_byte_array_append(bytes, &val, 1);
            punct = q + 1;
            if (*punct) {
                /*
                 * Make sure the character after
                 * the second hex digit is a byte
                 * separator, i.e. that we don't have
                 * more than two hex digits, or a
                 * bogus character.
                 */
                if (is_byte_sep(*punct)) {
                    p = punct + 1;
                    continue;
                }
                else if (force_separators) {
                    return false;
                }
            }
            p = punct;
            continue;
        }
        else if (*q && g_ascii_isxdigit(*p) && is_byte_sep(*q)) {
            one_digit[0] = *p;
            one_digit[1] = '\0';

            /*
             * Only one hex digit (not at the end of the string)
             */
            val = (uint8_t) strtoul(one_digit, NULL, 16);
            g_byte_array_append(bytes, &val, 1);
            p = q + 1;
            continue;
        }
        else if (!*q && g_ascii_isxdigit(*p)) {
            one_digit[0] = *p;
            one_digit[1] = '\0';

            /*
             * Only one hex digit (at the end of the string)
             */
            val = (uint8_t) strtoul(one_digit, NULL, 16);
            g_byte_array_append(bytes, &val, 1);
            p = q;
            continue;
        }
        else {
            return false;
        }
    }
    return true;
}

static inline char get_valid_byte_sep(char c, const unsigned encoding)
{
    char retval = -1; /* -1 means failure */

    switch (c) {
        case ':':
            if (encoding & ENC_SEP_COLON)
                retval = c;
            break;
        case '-':
            if (encoding & ENC_SEP_DASH)
                retval = c;
            break;
        case '.':
            if (encoding & ENC_SEP_DOT)
                retval = c;
            break;
        case ' ':
            if (encoding & ENC_SEP_SPACE)
                retval = c;
            break;
        case '\0':
            /* we were given the end of the string, so it's fine */
            retval = 0;
            break;
        default:
            if (g_ascii_isxdigit(c) && (encoding & ENC_SEP_NONE))
                retval = 0;
            /* anything else means we've got a failure */
            break;
    }

    return retval;
}

/* Turn a string of hex digits with optional separators (defined by is_byte_sep())
 * into a byte array. Unlike hex_str_to_bytes(), this will read as many hex-char
 * pairs as possible and not error if it hits a non-hex-char; instead it just ends
 * there. (i.e., like strtol()/atoi()/etc.) Unless fail_if_partial is true.
 *
 * The **endptr, if not NULL, is set to the char after the last hex character.
 */
bool hex_str_to_bytes_encoding(const char *hex_str, GByteArray *bytes, const char **endptr,
                          const unsigned encoding, const bool fail_if_partial)
{
    int8_t c, d;
    uint8_t val;
    const char *end = hex_str;
    bool retval = false;
    char sep = -1;

    /* a map from ASCII hex chars to their value */
    static const int8_t str_to_nibble[256] = {
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
        -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    };

    /* we must see two hex chars at the beginning, or fail */
    if (bytes && *end && g_ascii_isxdigit(*end) && g_ascii_isxdigit(*(end+1))) {
        retval = true;

        /* set the separator character we'll allow; if this returns a -1, it means something's
         * invalid after the hex, but we'll let the while-loop grab the first hex-pair anyway
         */
        sep = get_valid_byte_sep(*(end+2), encoding);

        while (*end) {
            c = str_to_nibble[(unsigned char)*end];
            if (c < 0) {
                if (fail_if_partial) retval = false;
                break;
            }

            d = str_to_nibble[(unsigned char)*(end+1)];
            if (d < 0) {
                if (fail_if_partial) retval = false;
                break;
            }
            val = ((uint8_t)c * 16) + d;
            g_byte_array_append(bytes, &val, 1);
            end += 2;

            /* check for separator and peek at next char to make sure we should keep going */
            if (sep > 0 && *end == sep && str_to_nibble[(unsigned char)*(end+1)] > -1) {
                /* yes, it's the right sep and followed by more hex, so skip the sep */
                ++end;
            } else if (sep != 0 && *end) {
                /* we either need a separator, but we don't see one; or the get_valid_byte_sep()
                   earlier didn't find a valid one to begin with */
                if (fail_if_partial) retval = false;
                break;
            }
            /* otherwise, either no separator allowed, or *end is null, or *end is an invalid
             * sep, or *end is a valid sep but after it is not a hex char - in all those
             * cases, just loop back up and let it fail later naturally.
             */
        }
    }

    if (!retval) {
        if (bytes) g_byte_array_set_size(bytes, 0);
        end = hex_str;
    }

    if (endptr) *endptr = end;

    return retval;
}


/**
 * Create a copy of a GByteArray
 *
 * @param ba The byte array to be copied.
 * @return If ba exists, a freshly allocated copy.  NULL otherwise.
 *
 */
GByteArray *byte_array_dup(const GByteArray *ba)
{
    GByteArray *new_ba;

    if (!ba)
        return NULL;

    new_ba = g_byte_array_new();
    g_byte_array_append(new_ba, ba->data, ba->len);
    return new_ba;
}

/**
 * Compare the contents of two GByteArrays
 *
 * @param ba1 A byte array
 * @param ba2 A byte array
 * @return If both arrays are non-NULL and their lengths are equal and
 *         their contents are equal, returns true.  Otherwise, returns
 *         false.
 *
 * XXX - Should this be in strutil.c?
 */
bool byte_array_equal(GByteArray *ba1, GByteArray *ba2)
{
    if (!ba1 || !ba2)
        return false;

    if (ba1->len != ba2->len)
        return false;

    if (memcmp(ba1->data, ba2->data, ba1->len) != 0)
        return false;

    return true;
}



/* chars allowed: lower case letters, digits, '-', "_", and ".". */
static const uint8_t module_valid_chars_lower_case[256] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x0F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x1F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, /* 0x20-0x2F '-', '.'      */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30-0x3F '0'-'9'       */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x4F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 0x50-0x5F '_' */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60-0x6F 'a'-'o'       */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x70-0x7F 'p'-'z'       */
    /* upper 128 empty-initialized to 0 */
};

/* chars allowed: alphanumerics, '-', "_", and ".". */
static const uint8_t module_valid_chars[256] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x0F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x1F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, /* 0x20-0x2F '-', '.'      */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30-0x3F '0'-'9'       */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40-0x4F 'A'-'O'       */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 0x50-0x5F 'P'-'Z', '_' */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60-0x6F 'a'-'o'       */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x70-0x7F 'p'-'z'       */
    /* upper 128 empty-initialized to 0 */
};

unsigned char module_check_valid_name(const char *name, bool lower_only)
{
    const char *p = name;
    unsigned char c = '.', lastc;
    const uint8_t *chars;

    /* First character cannot be '-'. */
    if (name[0] == '-')
        return '-';

    if (lower_only)
        chars = module_valid_chars_lower_case;
    else
        chars = module_valid_chars;

    do {
        lastc = c;
        c = *(p++);
        /* Leading '.' or substring ".." are disallowed. */
        if (c == '.' && lastc == '.') {
            break;
        }
    } while (chars[c]);

    /* Trailing '.' is disallowed. */
    if (lastc == '.') {
        return '.';
    }
    return c;
}

#ifdef __MINGW32__
//static int toupper(int c)
//{
//    if (c >= 'a' && c <= 'z') {
//        c -= 'a' - 'A';
//    }
//    return c;
//}
//static int tolower(int c)
//{
//    if (c >= 'A' && c <= 'Z') {
//        c += 'a' - 'A';
//    }
//    return c;
//}
#endif

char *ascii_toupper(const char *str)
{
    int len = strlen(str);
    char *dst = malloc(len+1);

    for (int i = 0; i < len; i++) {
        dst[i] = toupper(str[i]);
    }
    dst[len] = '\0';

    return dst;
}

char *ascii_tolower(const char *str)
{
    int len = strlen(str);
    char *dst = malloc(len+1);

    for (int i = 0; i < len; i++) {
        dst[i] = tolower(str[i]);
    }
    dst[len] = '\0';

    return dst;
}

#define STRDUP_VPRINTF_DEFAULT_BUFFER 256
char *strdup_vprintf(const char *fmt, va_list ap)
{
    va_list ap2;
    char buf[STRDUP_VPRINTF_DEFAULT_BUFFER];
    int needed_len;
    char *new_buf;
    size_t new_buf_size;

    va_copy(ap2, ap);
    needed_len = vsnprintf(buf, sizeof(buf), fmt, ap2);
    va_end(ap2);

    new_buf_size = needed_len + 1;
    new_buf = malloc(new_buf_size);

    if (new_buf_size <= STRDUP_VPRINTF_DEFAULT_BUFFER) {
        memcpy(new_buf, buf, new_buf_size);
        return new_buf;
    }
    vsnprintf(new_buf, new_buf_size, fmt, ap);
    return new_buf;

}

static const char _hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
                              '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

const int _utf8_seqlen[256] = {
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x00...0x0f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x10...0x1f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x20...0x2f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x30...0x3f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x40...0x4f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x50...0x5f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x60...0x6f */
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* 0x70...0x7f */
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  /* 0x80...0x8f */
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  /* 0x90...0x9f */
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  /* 0xa0...0xaf */
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  /* 0xb0...0xbf */
    0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  /* 0xc0...0xcf */
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  /* 0xd0...0xdf */
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,  /* 0xe0...0xef */
    4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,  /* 0xf0...0xff */
};


/*
 * Copy byte by byte without UTF-8 truncation (assume valid UTF-8 input).
 * Return byte size written, or that would have been
 * written with enough space.
 */
size_t label_strcpy(char *label_str, size_t buf_size, size_t pos,
                const uint8_t *str, int flags)
{
    if (pos >= buf_size)
        return pos;

    uint8_t r = 0;
    ssize_t chlen;
    ssize_t idx, src_len;
    ssize_t free_len;

    label_str[pos] = '\0';

    if(str == NULL) {
        return pos;
    }

    idx = 0;
    src_len = strlen(str);
    free_len = buf_size - pos - 1;

    while (idx < src_len) {
        chlen = _utf8_seqlen[str[idx]];
        if (chlen <= 0) {
            /* We were passed invalid UTF-8. This is an error. Complain and do... something. */
//            ws_log_utf8(str, -1, NULL);
            /*
             * XXX If we are going to return here instead of trying to recover maybe the log level should
             * be higher than DEBUG.
             */
            return pos;
        }

        /* ASCII */
        if (chlen == 1) {
            if (flags & FORMAT_LABEL_REPLACE_SPACE && g_ascii_isspace(str[idx])) {
                if (free_len >= 1) {
                    label_str[pos] = ' ';
                    label_str[pos+1] = '\0';
                }
                pos++;
                idx++;
                free_len--;
                continue;
            }

            r = 0;
            switch (str[idx]) {
                case '\a': r = 'a'; break;
                case '\b': r = 'b'; break;
                case '\f': r = 'f'; break;
                case '\n': r = 'n'; break;
                case '\r': r = 'r'; break;
                case '\t': r = 't'; break;
                case '\v': r = 'v'; break;
            }
            if (r != 0) {
                if (free_len >= 2) {
                    label_str[pos] = '\\';
                    label_str[pos+1] = r;
                    label_str[pos+2] = '\0';
                }
                pos += 2;
                idx += 1;
                free_len -= 2;
                continue;
            }

            if (g_ascii_isprint(str[idx])) {
                if (free_len >= 1) {
                    label_str[pos] = str[idx];
                    label_str[pos+1] = '\0';
                }
                pos++;
                idx++;
                free_len--;
                continue;
            }

            if (free_len >= 4) {
                label_str[pos+0] = '\\';
                label_str[pos+1] = 'x';

                uint8_t ch = str[idx];
                label_str[pos+2] = _hex[ch >> 4];
                label_str[pos+3] = _hex[ch & 0x0F];
                label_str[pos+4] = '\0';
            }
            pos += 4;
            idx += chlen;
            free_len -= 4;
            continue;
        }

        /* UTF-8 multibyte */
        if (chlen == 2 && str[idx] == 0xC2 &&
                                str[idx+1] >= 0x80 && str[idx+1] <= 0x9F) {
            /*
             * Escape the C1 control codes. C0 (covered above) and C1 are
             * inband signalling and transparent to Unicode.
             * Anything else probably has text semantics should not be removed.
             */
            /*
             * Special case: The second UTF-8 byte is the same as the Unicode
             * code point for range U+0080 - U+009F.
             */
            if (free_len >= 6) {
                label_str[pos+0] = '\\';
                label_str[pos+1] = 'u';
                label_str[pos+2] = '0';
                label_str[pos+3] = '0';

                uint8_t ch = str[idx+1];
                label_str[pos+4] = _hex[ch >> 4];
                label_str[pos+5] = _hex[ch & 0x0F];
                label_str[pos+6] = '\0';
            }
            pos += 6;
            idx += chlen;
            free_len -= 6;
            continue;
        }

        /* Just copy */
        if (free_len >= chlen) {
            for (ssize_t j = 0; j < chlen; j++) {
                label_str[pos+j] = str[idx+j];
            }
            label_str[pos+chlen] = '\0';
        }
        pos += chlen;
        idx += chlen;
        free_len -= chlen;
    }

    return pos;
}

size_t label_strcat(char *label_str, size_t bufsize, const uint8_t *str, int flags)
{
    return label_strcpy(label_str, bufsize, strlen(label_str), str, flags);
}


char printable_char_or_period(char c)
{
    return g_ascii_isprint(c) ? c : '.';
}

/*
 * This is used by the display filter engine and must be compatible
 * with display filter syntax.
 */
static inline bool escape_char(char c, char *p)
{
    int r = -1;

    /*
     * backslashes and double-quotes must be escaped (double-quotes
     * are escaped by passing '"' as quote_char in escape_string_len)
     * whitespace is also escaped.
     */
    switch (c) {
        case '\a': r = 'a'; break;
        case '\b': r = 'b'; break;
        case '\f': r = 'f'; break;
        case '\n': r = 'n'; break;
        case '\r': r = 'r'; break;
        case '\t': r = 't'; break;
        case '\v': r = 'v'; break;
        case '\\': r = '\\'; break;
        case '\0': r = '0'; break;
    }

    if (r != -1) {
        *p = r;
        return true;
    }
    return false;
}

static inline bool escape_null(char c, char *p)
{
    if (c == '\0') {
        *p = '0';
        return true;
    }
    return false;
}

static char *escape_string_len(const char *string, ssize_t len,
                    bool (*escape_func)(char c, char *p), bool add_quotes,
                    char quote_char, bool double_quote)
{
    char c, r;
    wmem_strbuf_t *buf;
    size_t alloc_size;
    ssize_t i;

    if (len < 0)
        len = strlen(string);

    alloc_size = len;
    if (add_quotes)
        alloc_size += 2;

    buf = wmem_strbuf_new_sized(alloc_size);

    if (add_quotes && quote_char != '\0')
        wmem_strbuf_append_c(buf, quote_char);

    for (i = 0; i < len; i++) {
        c = string[i];
        if ((escape_func(c, &r))) {
            wmem_strbuf_append_c(buf, '\\');
            wmem_strbuf_append_c(buf, r);
        }
        else if (c == quote_char && quote_char != '\0') {
            /* If quoting, we must escape the quote_char somehow. */
            if (double_quote) {
                wmem_strbuf_append_c(buf, c);
                wmem_strbuf_append_c(buf, c);
            } else {
                wmem_strbuf_append_c(buf, '\\');
                wmem_strbuf_append_c(buf, c);
            }
        }
        else if (c == '\\' && quote_char != '\0' && !double_quote) {
            /* If quoting, and escaping the quote_char with a backslash,
             * then backslash must be escaped, even if escape_func doesn't. */
            wmem_strbuf_append_c(buf, '\\');
            wmem_strbuf_append_c(buf, '\\');
        }
        else {
            /* Other UTF-8 bytes are passed through. */
            wmem_strbuf_append_c(buf, c);
        }
    }

    if (add_quotes && quote_char != '\0')
        wmem_strbuf_append_c(buf, quote_char);

    return wmem_strbuf_finalize(buf);
}

char *ws_escape_string_len(const char *string, ssize_t len, bool add_quotes)
{
    return escape_string_len(string, len, escape_char, add_quotes, '"', false);
}

char *ws_escape_string(const char *string, bool add_quotes)
{
    return escape_string_len(string, -1, escape_char, add_quotes, '"', false);
}

char *ws_escape_null(const char *string, size_t len, bool add_quotes)
{
    /* XXX: The existing behavior (maintained) here is not to escape
     * backslashes even though NUL is escaped.
     */
    return escape_string_len(string, len, escape_null, add_quotes, add_quotes ? '"' : '\0', false);
}

#define    INITIAL_FMTBUF_SIZE    128

/*
 * Declare, and initialize, the variables used for an output buffer.
 */
#define FMTBUF_VARS \
    char *fmtbuf = (char*)malloc(INITIAL_FMTBUF_SIZE); \
    unsigned fmtbuf_len = INITIAL_FMTBUF_SIZE; \
    unsigned column = 0

/*
 * Expand the buffer to be large enough to add nbytes bytes, plus a
 * terminating '\0'.
 */
#define FMTBUF_EXPAND(nbytes) \
    /* \
     * Is there enough room for those bytes and also enough room for \
     * a terminating '\0'? \
     */ \
    if (column+(nbytes+1) >= fmtbuf_len) { \
        /* \
         * Double the buffer's size if it's not big enough. \
         * The size of the buffer starts at 128, so doubling its size \
         * adds at least another 128 bytes, which is more than enough \
         * for one more character plus a terminating '\0'. \
         */ \
        fmtbuf_len *= 2; \
        fmtbuf = (char *)realloc(fmtbuf, fmtbuf_len); \
    }

/*
 * Put a byte into the buffer; space must have been ensured for it.
 */
#define FMTBUF_PUTCHAR(b) \
    fmtbuf[column] = (b); \
    column++

/*
 * Add the one-byte argument, as an octal escape sequence, to the end
 * of the buffer.
 */
#define FMTBUF_PUTBYTE_OCTAL(b) \
    FMTBUF_PUTCHAR((((b)>>6)&03) + '0'); \
    FMTBUF_PUTCHAR((((b)>>3)&07) + '0'); \
    FMTBUF_PUTCHAR((((b)>>0)&07) + '0')

/*
 * Add the one-byte argument, as a hex escape sequence, to the end
 * of the buffer.
 */
#define FMTBUF_PUTBYTE_HEX(b) \
    FMTBUF_PUTCHAR('\\'); \
    FMTBUF_PUTCHAR('x'); \
    FMTBUF_PUTCHAR(hex[((b) >> 4) & 0xF]); \
    FMTBUF_PUTCHAR(hex[((b) >> 0) & 0xF])

/*
 * Put the trailing '\0' at the end of the buffer.
 */
#define FMTBUF_ENDSTR \
    fmtbuf[column] = '\0'

static char *format_text_internal(const unsigned char *string, size_t len,
                        bool replace_space)
{
    FMTBUF_VARS;
    const unsigned char *stringend = string + len;
    unsigned char c;

    while (string < stringend) {
        /*
         * Get the first byte of this character.
         */
        c = *string++;
        if (g_ascii_isprint(c)) {
            /*
             * Printable ASCII, so not part of a multi-byte UTF-8 sequence.
             * Make sure there's enough room for one more byte, and add
             * the character.
             */
            FMTBUF_EXPAND(1);
            FMTBUF_PUTCHAR(c);
        } else if (replace_space && g_ascii_isspace(c)) {
            /*
             * ASCII, so not part of a multi-byte UTF-8 sequence, but
             * not printable, but is a space character; show it as a
             * blank.
             *
             * Make sure there's enough room for one more byte, and add
             * the blank.
             */
            FMTBUF_EXPAND(1);
            FMTBUF_PUTCHAR(' ');
        } else if (c < 128) {
            /*
             * ASCII, so not part of a multi-byte UTF-8 sequence, but not
             * printable.
             *
             * That requires a minimum of 2 bytes, one for the backslash
             * and one for a letter, so make sure we have enough room
             * for that, plus a trailing '\0'.
             */
            FMTBUF_EXPAND(2);
            FMTBUF_PUTCHAR('\\');
            switch (c) {

                case '\a':
                    FMTBUF_PUTCHAR('a');
                    break;

                case '\b':
                    FMTBUF_PUTCHAR('b'); /* BS */
                    break;

                case '\f':
                    FMTBUF_PUTCHAR('f'); /* FF */
                    break;

                case '\n':
                    FMTBUF_PUTCHAR('n'); /* NL */
                    break;

                case '\r':
                    FMTBUF_PUTCHAR('r'); /* CR */
                    break;

                case '\t':
                    FMTBUF_PUTCHAR('t'); /* tab */
                    break;

                case '\v':
                    FMTBUF_PUTCHAR('v');
                    break;

                default:
                    /*
                     * We've already put the backslash, but this
                     * will put 3 more characters for the octal
                     * number; make sure we have enough room for
                     * that, plus the trailing '\0'.
                     */
                    FMTBUF_EXPAND(3);
                    FMTBUF_PUTBYTE_OCTAL(c);
                    break;
            }
        } else {
            /*
             * We've fetched the first byte of a multi-byte UTF-8
             * sequence into c.
             */
            int utf8_len;
            unsigned char mask;
            gunichar uc;
            unsigned char first;

            if ((c & 0xe0) == 0xc0) {
                /* Starts a 2-byte UTF-8 sequence; 1 byte left */
                utf8_len = 1;
                mask = 0x1f;
            } else if ((c & 0xf0) == 0xe0) {
                /* Starts a 3-byte UTF-8 sequence; 2 bytes left */
                utf8_len = 2;
                mask = 0x0f;
            } else if ((c & 0xf8) == 0xf0) {
                /* Starts a 4-byte UTF-8 sequence; 3 bytes left */
                utf8_len = 3;
                mask = 0x07;
            } else if ((c & 0xfc) == 0xf8) {
                /* Starts an old-style 5-byte UTF-8 sequence; 4 bytes left */
                utf8_len = 4;
                mask = 0x03;
            } else if ((c & 0xfe) == 0xfc) {
                /* Starts an old-style 6-byte UTF-8 sequence; 5 bytes left */
                utf8_len = 5;
                mask = 0x01;
            } else {
                /* 0xfe or 0xff or a continuation byte - not valid */
                utf8_len = -1;
            }
            if (utf8_len > 0) {
                /* Try to construct the Unicode character */
                uc = c & mask;
                for (int i = 0; i < utf8_len; i++) {
                    if (string >= stringend) {
                        /*
                         * Ran out of octets, so the character is
                         * incomplete.  Put in a REPLACEMENT CHARACTER
                         * instead, and then continue the loop, which
                         * will terminate.
                         */
                        uc = UNICODE_REPLACEMENT_CHARACTER;
                        break;
                    }
                    c = *string;
                    if ((c & 0xc0) != 0x80) {
                        /*
                         * Not valid UTF-8 continuation character; put in
                         * a replacement character, and then re-process
                         * this octet as the beginning of a new character.
                         */
                        uc = UNICODE_REPLACEMENT_CHARACTER;
                        break;
                    }
                    string++;
                    uc = (uc << 6) | (c & 0x3f);
                }

                /*
                 * If this isn't a valid Unicode character, put in
                 * a REPLACEMENT CHARACTER.
                 */
                if (!g_unichar_validate(uc))
                    uc = UNICODE_REPLACEMENT_CHARACTER;
            } else {
                /* 0xfe or 0xff; put it a REPLACEMENT CHARACTER */
                uc = UNICODE_REPLACEMENT_CHARACTER;
            }

            /*
             * OK, is it a printable Unicode character?
             */
            if (g_unichar_isprint(uc)) {
                /*
                 * Yes - put it into the string as UTF-8.
                 * This means that if it was an overlong
                 * encoding, this will put out the right
                 * sized encoding.
                 */
                if (uc < 0x80) {
                    first = 0;
                    utf8_len = 1;
                } else if (uc < 0x800) {
                    first = 0xc0;
                    utf8_len = 2;
                } else if (uc < 0x10000) {
                    first = 0xe0;
                    utf8_len = 3;
                } else if (uc < 0x200000) {
                    first = 0xf0;
                    utf8_len = 4;
                } else if (uc < 0x4000000) {
                    /*
                     * This should never happen, as Unicode doesn't
                     * go that high.
                     */
                    first = 0xf8;
                    utf8_len = 5;
                } else {
                    /*
                     * This should never happen, as Unicode doesn't
                     * go that high.
                     */
                    first = 0xfc;
                    utf8_len = 6;
                }
                FMTBUF_EXPAND(utf8_len);
                for (int i = utf8_len - 1; i > 0; i--) {
                    fmtbuf[column + i] = (uc & 0x3f) | 0x80;
                    uc >>= 6;
                }
                fmtbuf[column] = uc | first;
                column += utf8_len;
            } else if (replace_space && g_unichar_isspace(uc)) {
                /*
                 * Not printable, but is a space character; show it
                 * as a blank.
                 *
                 * Make sure there's enough room for one more byte,
                 * and add the blank.
                 */
                FMTBUF_EXPAND(1);
                FMTBUF_PUTCHAR(' ');
            } else if (c < 128) {
                /*
                 * ASCII, but not printable.
                 * Yes, this could happen with an overlong encoding.
                 *
                 * That requires a minimum of 2 bytes, one for the
                 * backslash and one for a letter, so make sure we
                 * have enough room for that, plus a trailing '\0'.
                 */
                FMTBUF_EXPAND(2);
                FMTBUF_PUTCHAR('\\');
                switch (c) {

                    case '\a':
                        FMTBUF_PUTCHAR('a');
                        break;

                    case '\b':
                        FMTBUF_PUTCHAR('b'); /* BS */
                        break;

                    case '\f':
                        FMTBUF_PUTCHAR('f'); /* FF */
                        break;

                    case '\n':
                        FMTBUF_PUTCHAR('n'); /* NL */
                        break;

                    case '\r':
                        FMTBUF_PUTCHAR('r'); /* CR */
                        break;

                    case '\t':
                        FMTBUF_PUTCHAR('t'); /* tab */
                        break;

                    case '\v':
                        FMTBUF_PUTCHAR('v');
                        break;

                    default:
                        /*
                         * We've already put the backslash, but this
                         * will put 3 more characters for the octal
                         * number; make sure we have enough room for
                         * that, plus the trailing '\0'.
                         */
                        FMTBUF_EXPAND(3);
                        FMTBUF_PUTBYTE_OCTAL(c);
                        break;
                }
            } else {
                /*
                 * Unicode, but not printable, and not ASCII;
                 * put it out as \uxxxx or \Uxxxxxxxx.
                 */
                if (uc <= 0xFFFF) {
                    FMTBUF_EXPAND(6);
                    FMTBUF_PUTCHAR('\\');
                    FMTBUF_PUTCHAR('u');
                    FMTBUF_PUTCHAR(_hex[(uc >> 12) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 8) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 4) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 0) & 0xF]);
                } else {
                    FMTBUF_EXPAND(10);
                    FMTBUF_PUTCHAR('\\');
                    FMTBUF_PUTCHAR('U');
                    FMTBUF_PUTCHAR(_hex[(uc >> 28) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 24) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 20) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 16) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 12) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 8) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 4) & 0xF]);
                    FMTBUF_PUTCHAR(_hex[(uc >> 0) & 0xF]);
                }
            }
        }
    }

    FMTBUF_ENDSTR;

    return fmtbuf;
}

/*
 * Given a wmem scope, a not-necessarily-null-terminated string,
 * expected to be in UTF-8 but possibly containing invalid sequences
 * (as it may have come from packet data), and the length of the string,
 * generate a valid UTF-8 string from it, allocated in the specified
 * wmem scope, that:
 *
 *   shows printable Unicode characters as themselves;
 *
 *   shows non-printable ASCII characters as C-style escapes (octal
 *   if not one of the standard ones such as LF -> '\n');
 *
 *   shows non-printable Unicode-but-not-ASCII characters as
 *   their universal character names;
 *
 *   shows illegal UTF-8 sequences as a sequence of bytes represented
 *   as C-style hex escapes (XXX: Does not actually do this. Some illegal
 *   sequences, such as overlong encodings, the sequences reserved for
 *   UTF-16 surrogate halves (paired or unpaired), and values outside
 *   Unicode (i.e., the old sequences for code points above U+10FFFF)
 *   will be decoded in a permissive way. Other illegal sequences,
 *   such 0xFE and 0xFF and the presence of a continuation byte where
 *   not expected (or vice versa its absence), are replaced with
 *   REPLACEMENT CHARACTER.)
 *
 * and return a pointer to it.
 */
char *format_text(const char *string, size_t len)
{
    return format_text_internal(string, len, false);
}

/** Given a wmem scope and a null-terminated string, expected to be in
 *  UTF-8 but possibly containing invalid sequences (as it may have come
 *  from packet data), and the length of the string, generate a valid
 *  UTF-8 string from it, allocated in the specified wmem scope, that:
 *
 *   shows printable Unicode characters as themselves;
 *
 *   shows non-printable ASCII characters as C-style escapes (octal
 *   if not one of the standard ones such as LF -> '\n');
 *
 *   shows non-printable Unicode-but-not-ASCII characters as
 *   their universal character names;
 *
 *   shows illegal UTF-8 sequences as a sequence of bytes represented
 *   as C-style hex escapes;
 *
 *  and return a pointer to it.
 */
char *format_text_string(const char *string)
{
    return format_text_internal(string, strlen(string), false);
}

/*
 * Given a string, generate a string from it that shows non-printable
 * characters as C-style escapes except a whitespace character
 * (space, tab, carriage return, new line, vertical tab, or formfeed)
 * which will be replaced by a space, and return a pointer to it.
 */
char *format_text_wsp(const char *string, size_t len)
{
    return format_text_internal(string, len, true);
}

/*
 * Given a string, generate a string from it that shows non-printable
 * characters as the chr parameter passed, except a whitespace character
 * (space, tab, carriage return, new line, vertical tab, or formfeed)
 * which will be replaced by a space, and return a pointer to it.
 *
 * This does *not* treat the input string as UTF-8.
 *
 * This is useful for displaying binary data that frequently but not always
 * contains text; otherwise the number of C escape codes makes it unreadable.
 */
char *format_text_chr(const char *string, size_t len, char chr)
{
    char *buf, *q;

    buf = malloc(len + 1);
    q = buf;
    for (const char *p = string; p < string + len; p++) {
        if (g_ascii_isprint(*p)) {
            *q = *p;
        }
        else if (g_ascii_isspace(*p)) {
            *q = ' ';
        }
        else {
            *q = chr;
        }
    }
    return buf;
}

char *format_char(char c)
{
    char *buf;
    char r;

    if (g_ascii_isprint(c)) {
        buf = malloc(2);
        buf[0] = c;
        buf[1] = '\0';
        return buf;
    }
    if (escape_char(c, &r)) {
        buf = malloc(3);
        buf[0] = '\\';
        buf[1] = r;
        buf[2] = '\0';
        return buf;
    }
    buf = malloc(5);
    buf[0] = '\\';
    buf[1] = 'x';
    buf[2] = _hex[((uint8_t)c >> 4) & 0xF];
    buf[3] = _hex[((uint8_t)c >> 0) & 0xF];
    buf[4] = '\0';
    return buf;
}

/* Convert all ASCII letters to lower case, in place. */
char *ascii_strdown_inplace(char *str)
{
    char *s;

    for (s = str; *s; s++)
        /* What 'g_ascii_tolower (char c)' does, this should be slightly more efficient */
        *s = g_ascii_isupper (*s) ? *s - 'A' + 'a' : *s;

    return (str);
}

/* Convert all ASCII letters to upper case, in place. */
char *ascii_strup_inplace(char *str)
{
    char *s;

    for (s = str; *s; s++)
        /* What 'g_ascii_toupper (char c)' does, this should be slightly more efficient */
        *s = g_ascii_islower (*s) ? *s - 'a' + 'A' : *s;

    return (str);
}

/* Check if an entire UTF-8 string is printable. */
bool isprint_utf8_string(const char *str, const unsigned length)
{
    const char *strend = str + length;

    if (!g_utf8_validate(str, length, NULL)) {
        return false;
    }

    while (str < strend) {
        /* This returns false for G_UNICODE_CONTROL | G_UNICODE_FORMAT |
         * G_UNICODE_UNASSIGNED | G_UNICODE_SURROGATE
         * XXX: Could it be ok to have certain format characters, e.g.
         * U+00AD SOFT HYPHEN? If so, format_text() should be changed too.
         */
        if (!g_unichar_isprint(g_utf8_get_char(str))) {
            return false;
        }
        str = g_utf8_next_char(str);
    }

    return true;
}

/* Check if an entire string is digits. */
bool isdigit_string(const unsigned char *str)
{
    unsigned pos;

    /* Loop until we reach the end of the string (a null) */
    for(pos = 0; str[pos] != '\0'; pos++){
        if(!g_ascii_isdigit(str[pos])){
            /* The string contains a non-digit character */
            return false;
        }
    }

    /* The string contains only digits */
    return true;
}

static inline char low_nibble_of_octet_to_hex(uint8_t oct)
{
    /* At least one version of Apple's C compiler/linker is buggy, causing
       a complaint from the linker about the "literal C string section"
       not ending with '\0' if we initialize a 16-element "char" array with
       a 16-character string, the fact that initializing such an array with
       such a string is perfectly legitimate ANSI C nonwithstanding, the 17th
       '\0' byte in the string nonwithstanding. */
    static const char hex_digits[16] =
    { '0', '1', '2', '3', '4', '5', '6', '7',
      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    return hex_digits[oct & 0xF];
}

static inline char *byte_to_hex(char *out, uint32_t dword)
{
    *out++ = low_nibble_of_octet_to_hex(dword >> 4);
    *out++ = low_nibble_of_octet_to_hex(dword);
    return out;
}
char *bytes_to_hexstr(char *out, const uint8_t *ad, size_t len)
{
    size_t i;

    for (i = 0; i < len; i++)
        out = byte_to_hex(out, ad[i]);
    return out;
}

/*
 * This does *not* null-terminate the string.  It returns a pointer
 * to the position in the string following the last character it
 * puts there, so that the caller can either put the null terminator
 * in or can append more stuff to the buffer.
 *
 * There needs to be at least len * 3 - 1 bytes left in the buffer.
 */
char *bytes_to_hexstr_punct(char *out, const uint8_t *ad, size_t len, char punct)
{
    size_t i;

    if(!ad) {
        return NULL;
    }

    out = byte_to_hex(out, ad[0]);
    for (i = 1; i < len; i++) {
        *out++ = punct;
        out = byte_to_hex(out, ad[i]);
    }
    return out;
}

/* Routine to convert a sequence of bytes to a hex string, one byte/two hex
 * digits at a time, with a specified punctuation character between
 * the bytes.
 *
 * If punct is '\0', no punctuation is applied (and thus
 * the resulting string is (len-1) bytes shorter)
 */
char *bytes_to_str_punct_maxlen(const uint8_t *src, size_t src_size,
            char punct, size_t max_bytes_len)
{
    char *buf;
    size_t max_char_size;
    char *buf_ptr;
    int truncated = 0;

    if (!src) {
        return NULL;
    }

    if (!src_size) {
        return strdup("");
    }

    if (!punct)
        return bytes_to_str_maxlen(src, src_size, max_bytes_len);

    if (max_bytes_len == 0 || max_bytes_len > src_size) {
        max_bytes_len = src_size;
    }
    else if (max_bytes_len < src_size) {
        truncated = 1;
    }

    /* Include space for ellipsis and '\0'. Optional extra punct
     * at the end is already accounted for. */
    max_char_size = max_bytes_len * 3 + strlen(UTF8_HORIZONTAL_ELLIPSIS) + 1;

    buf = malloc(max_char_size);
    buf_ptr = bytes_to_hexstr_punct(buf, src, max_bytes_len, punct);

    if (truncated) {
        *buf_ptr++ = punct;
        buf_ptr = g_stpcpy(buf_ptr, UTF8_HORIZONTAL_ELLIPSIS);
    }

    *buf_ptr = '\0';
    return buf;
}

char *bytes_to_str_maxlen(const uint8_t *src, size_t src_size,
            size_t max_bytes_len)
{
    char *buf;
    size_t max_char_size;
    char *buf_ptr;
    int truncated = 0;

    if (!src) {
        return NULL;
    }

    if (!src_size) {
        return strdup("");
    }

    if (max_bytes_len == 0 || max_bytes_len > src_size) {
        max_bytes_len = src_size;
    }
    else if (max_bytes_len < src_size) {
        truncated = 1;
    }

    max_char_size = max_bytes_len * 2 + strlen(UTF8_HORIZONTAL_ELLIPSIS) + 1;

    buf = malloc(max_char_size);
    buf_ptr = bytes_to_hexstr(buf, src, max_bytes_len);

    if (truncated)
        buf_ptr = g_stpcpy(buf_ptr, UTF8_HORIZONTAL_ELLIPSIS);

    *buf_ptr = '\0';
    return buf;
}

char *uint8_to_hex(char *out, uint8_t val)
{
    return byte_to_hex(out, val);
}

char *word_to_hex(char *out, uint16_t word)
{
    out = byte_to_hex(out, word >> 8);
    out = byte_to_hex(out, word);
    return out;
}

char *word_to_hex_punct(char *out, uint16_t word, char punct)
{
    out = byte_to_hex(out, word >> 8);
    *out++ = punct;
    out = byte_to_hex(out, word);
    return out;
}

char *word_to_hex_npad(char *out, uint16_t word)
{
    if (word >= 0x1000)
        *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 12));
    if (word >= 0x0100)
        *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 8));
    if (word >= 0x0010)
        *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 4));
    *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 0));
    return out;
}

char *dword_to_hex(char *out, uint32_t dword)
{
    out = word_to_hex(out, dword >> 16);
    out = word_to_hex(out, dword);
    return out;
}

char *dword_to_hex_punct(char *out, uint32_t dword, char punct)
{
    out = word_to_hex_punct(out, dword >> 16, punct);
    *out++ = punct;
    out = word_to_hex_punct(out, dword, punct);
    return out;
}

char *qword_to_hex(char *out, uint64_t qword)
{
    out = dword_to_hex(out, (uint32_t)(qword >> 32));
    out = dword_to_hex(out, (uint32_t)(qword & 0xffffffff));
    return out;
}

char *qword_to_hex_punct(char *out, uint64_t qword, char punct)
{
    out = dword_to_hex_punct(out, (uint32_t)(qword >> 32), punct);
    *out++ = punct;
    out = dword_to_hex_punct(out, (uint32_t)(qword & 0xffffffff), punct);
    return out;
}

/*
 * The *_to_str_back() functions measured approx. a x7.5 speed-up versus
 * snprintf() on my Linux system with GNU libc.
 */
char *oct_to_str_back(char *ptr, uint32_t value)
{
    while (value) {
        *(--ptr) = '0' + (value & 0x7);
        value >>= 3;
    }

    *(--ptr) = '0';
    return ptr;
}

char *oct64_to_str_back(char *ptr, uint64_t value)
{
    while (value) {
        *(--ptr) = '0' + (value & 0x7);
        value >>= 3;
    }

    *(--ptr) = '0';
    return ptr;
}

char *hex_to_str_back_len(char *ptr, uint32_t value, int len)
{
    do {
        *(--ptr) = low_nibble_of_octet_to_hex(value);
        value >>= 4;
        len--;
    } while (value);

    /* pad */
    while (len > 0) {
        *(--ptr) = '0';
        len--;
    }

    *(--ptr) = 'x';
    *(--ptr) = '0';

    return ptr;
}

char *hex64_to_str_back_len(char *ptr, uint64_t value, int len)
{
    do {
        *(--ptr) = low_nibble_of_octet_to_hex(value & 0xF);
        value >>= 4;
        len--;
    } while (value);

    /* pad */
    while (len > 0) {
        *(--ptr) = '0';
        len--;
    }

    *(--ptr) = 'x';
    *(--ptr) = '0';

    return ptr;
}

char *uint_to_str_back(char *ptr, uint32_t value)
{
    char const *p;

    /* special case */
    if (value == 0)
        *(--ptr) = '0';

    while (value >= 10) {
        p = fast_strings[100 + (value % 100)];

        value /= 100;

        *(--ptr) = p[2];
        *(--ptr) = p[1];
    }

    if (value)
        *(--ptr) = (value) | '0';

    return ptr;
}

char *uint64_to_str_back(char *ptr, uint64_t value)
{
    char const *p;

    /* special case */
    if (value == 0)
        *(--ptr) = '0';

    while (value >= 10) {
        p = fast_strings[100 + (value % 100)];

        value /= 100;

        *(--ptr) = p[2];
        *(--ptr) = p[1];
    }

    /* value will be 0..9, so using '& 0xF' is safe, and faster than '% 10' */
    if (value)
        *(--ptr) = (value & 0xF) | '0';

    return ptr;
}

char *uint_to_str_back_len(char *ptr, uint32_t value, int len)
{
    char *new_ptr;

    new_ptr = uint_to_str_back(ptr, value);

    /* subtract from len number of generated characters */
    len -= (int)(ptr - new_ptr);

    /* pad remaining with '0' */
    while (len > 0) {
        *(--new_ptr) = '0';
        len--;
    }

    return new_ptr;
}

char *uint64_to_str_back_len(char *ptr, uint64_t value, int len)
{
    char *new_ptr;

    new_ptr = uint64_to_str_back(ptr, value);

    /* subtract from len number of generated characters */
    len -= (int)(ptr - new_ptr);

    /* pad remaining with '0' */
    while (len > 0) {
        *(--new_ptr) = '0';
        len--;
    }

    return new_ptr;
}

char *int_to_str_back(char *ptr, int32_t value)
{
    if (value < 0) {
        ptr = uint_to_str_back(ptr, -value);
        *(--ptr) = '-';
    } else
        ptr = uint_to_str_back(ptr, value);

    return ptr;
}

char *int64_to_str_back(char *ptr, int64_t value)
{
    if (value < 0) {
        ptr = uint64_to_str_back(ptr, -value);
        *(--ptr) = '-';
    } else
        ptr = uint64_to_str_back(ptr, value);

    return ptr;
}

static size_t uint32_to_str_buf_len(const uint32_t u)
{
    /* ((2^32)-1) == 2147483647 */
    if (u >= 1000000000)return 10;
    if (u >= 100000000) return 9;
    if (u >= 10000000)  return 8;
    if (u >= 1000000)   return 7;
    if (u >= 100000)    return 6;
    if (u >= 10000)     return 5;
    if (u >= 1000)      return 4;
    if (u >= 100)       return 3;
    if (u >= 10)        return 2;

    return 1;
}

void uint32_to_str_buf(uint32_t u, char *buf, size_t buf_len)
{
    size_t str_len = uint32_to_str_buf_len(u)+1;

    char *bp = &buf[str_len];

    _return_if_nospace(str_len, buf, buf_len);

    *--bp = '\0';

    uint_to_str_back(bp, u);
}

static size_t uint64_to_str_buf_len(const uint64_t u)
{
    /* ((2^64)-1) == 18446744073709551615 */

    if (u >= UINT64_C(10000000000000000000)) return 20;
    if (u >= UINT64_C(1000000000000000000))  return 19;
    if (u >= UINT64_C(100000000000000000))   return 18;
    if (u >= UINT64_C(10000000000000000))    return 17;
    if (u >= UINT64_C(1000000000000000))     return 16;
    if (u >= UINT64_C(100000000000000))      return 15;
    if (u >= UINT64_C(10000000000000))       return 14;
    if (u >= UINT64_C(1000000000000))        return 13;
    if (u >= UINT64_C(100000000000))         return 12;
    if (u >= UINT64_C(10000000000))          return 11;
    if (u >= UINT64_C(1000000000))           return 10;
    if (u >= UINT64_C(100000000))            return 9;
    if (u >= UINT64_C(10000000))             return 8;
    if (u >= UINT64_C(1000000))              return 7;
    if (u >= UINT64_C(100000))               return 6;
    if (u >= UINT64_C(10000))                return 5;
    if (u >= UINT64_C(1000))                 return 4;
    if (u >= UINT64_C(100))                  return 3;
    if (u >= UINT64_C(10))                   return 2;

    return 1;
}

void uint64_to_str_buf(uint64_t u, char *buf, size_t buf_len)
{
    size_t str_len = uint64_to_str_buf_len(u)+1;

    char *bp = &buf[str_len];

    _return_if_nospace(str_len, buf, buf_len);

    *--bp = '\0';

    uint64_to_str_back(bp, u);
}

static const uint8_t EBCDIC_translate_ASCII [ 256 ] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
    0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    0x2E, 0x2E, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
    0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x2E, 0x3F,
    0x20, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x2E, 0x2E, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
    0x26, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x2E, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E,
    0x2D, 0x2F, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x2E, 0x7C, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
    0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x2E, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
    0x2E, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
    0x68, 0x69, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
    0x71, 0x72, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
    0x79, 0x7A, 0x2E, 0x2E, 0x2E, 0x5B, 0x2E, 0x2E,
    0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x5D, 0x2E, 0x2E,
    0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
    0x48, 0x49, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
    0x51, 0x52, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x5C, 0x2E, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
    0x59, 0x5A, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
    0x38, 0x39, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E
};

void EBCDIC_to_ASCII(uint8_t *buf, unsigned bytes)
{
    unsigned   i;
    uint8_t *bufptr;

    bufptr = buf;

    for (i = 0; i < bytes; i++, bufptr++) {
        *bufptr = EBCDIC_translate_ASCII[*bufptr];
    }
}

uint8_t EBCDIC_to_ASCII1(uint8_t c)
{
    return EBCDIC_translate_ASCII[c];
}


union hexfloat {
    float f;
    struct {
        unsigned franction:23;
        unsigned exp:8;
        unsigned sign:1;
    }bits;
};

union hexdouble {
    double d;
    struct {
        uint64_t fraction:52;
        uint64_t exp:11;
        uint64_t sign:1;
    }bits;
};

static char *stripr(char *str, char ch)
{
    int len, i;
    len = strlen(str);
    for (i = len-1; i >= 0; i--) {
        if (str[i] == ch) {
            str[i] = '\0';
        } else {
            break;
        }
    }

    return str;
}

char *float_to_hexfp(char *dst, int size, float val)
{
    char dstr[64];
    union hexfloat hd;
    hd.f = val;
    snprintf(dstr, 64, PRIu32, (uint32_t)(hd.bits.franction<<9));
    stripr(dstr, '0');
    snprintf(dst, size, "0x1.%sp%+d", dstr, 127-hd.bits.exp);

    return dst;
}

char *double_to_hexfp(char *dst, int size, double val)
{
    char dstr[64];
    union hexdouble hd;
    hd.d = val;
    snprintf(dstr, 64, PRIu64, (uint64_t)(hd.bits.fraction<<12));
    stripr(dstr, '0');
    snprintf(dst, size, "0x1.%sp%+d", dstr, 1023-hd.bits.exp);

    return dst;
}
